<?php
///////////////////////////////////////////////////////////////////////////////////////////////////////
//  这个文件是 JCAT PHP框架的一部，该项目和此文件 均遵循 GNU 自由软件协议
// 
//  Copyleft 2008 JeCat.cn(http://team.JeCat.cn)
//
//
//  JCAT PHP框架 的正式全名是：Jellicle Cat PHP Framework。
//  “Jellicle Cat”出自 Andrew Lloyd Webber的音乐剧《猫》（《Prologue:Jellicle Songs for Jellicle Cats》）。
//  JCAT 是一个开源项目，它像音乐剧中的猫一样自由，你可以毫无顾忌地使用JCAT PHP框架。JCAT 由中国团队开发维护。
//  正在使用的这个版本是：0.5.0 / SVN信息: $Id: class.JCAT_UIHtmlNodeParser.php 1603 2009-03-31 15:35:42Z alee $
//
//
//
//  相关的链接：
//    [主页] http://jcat.JeCat.cn
//    [下载(HTTP)] http://code.google.com/p/jcat-php/downloads/list
//    [下载(svn)] svn checkout http://jcat-php.googlecode.com/svn/branches/0.4.0/Framework/ JCAT0.4
//    [在线文档] http://jcat.JeCat.cn/document
//    [社区] http://jj.jecat.cn/forum-7-1.html
//  不很相关：
//    [MP3] http://www.google.com/search?q=jellicle+songs+for+jellicle+cats+Andrew+Lloyd+Webber
//    [VCD/DVD] http://www.google.com/search?q=CAT+Andrew+Lloyd+Webber+video
//
///////////////////////////////////////////////////////////////////////////////////////////////////////
/*-- Project Introduce --*/
namespace JCAT ;





/**
 * What's this Class ?
 *
 * @author		alee
 * @access		public
 */
class UIHtmlNodeParser extends UIObjectParserBase implements IUIAutoloadableProcessor
{

	public function UIHtmlNodeParser()
	{
		$aCompilerManager = new UIObjectProcessorManager() ;
		
		// 自动载入 编译器目录下的 所有 编译器
		foreach(self::$arrCompilerDirs as $sDir)
		{
			$aCompilerManager->AddAutoloadProcessorDir($sDir) ;
		}

		$this->SetCompilerManager($aCompilerManager) ;
	}


	/**
	 * What's this Method ?
	 * 
	 * @access	public
	 * @param	$sDir	string	What's this Parameter ?
	 * @static
	 * @return	void
	 */
	static public function AddCompilerDir( $sDir ) 
	{
		ASSERT::ASSERT_DIR($sDir) ;

		$sDirPath = GlobalFunc::TidyPath($sDir) ;

		if( !in_array($sDirPath,self::$arrCompilerDirs) )
		{
			self::$arrCompilerDirs[] = $sDirPath ;
		}
	}



	/**
	 * What's this Method ?
	 * 
	 * @access	public
	 * @static
	 * @return	int
	 */
	static public function ClearCompilerDir() 
	{
		$nCount = count(self::$arrCompilerDirs) ;
		self::$arrCompilerDirs = array() ;
		return $nCount ;
	}



	/**
	 * 分析模版，找到并创建 UI对象
	 * 
	 * @access	public
	 * @param	$aUITemplate			UI		UI模版对象
	 * @param	$sTemplatePath			string		模板文件路径
	 * @param	& $sCompiled			string		in/out 前一分析器的分析结果输入，和此分析器的分析结果输出 
	 * @return	void
	 */
	public function Parse( UI $aUITemplate,$sTemplatePath, & $sCompiled )
	{
		ASSERT::ASSERT_FILE($sTemplatePath) ;
		ASSERT::ASSERT_STRING($sCompiled) ;
		
		// 查找分析 Node 的标签
		$this->_FindNodeTag($aUITemplate,$sCompiled,$sTemplatePath) ;
		
		// 用 标签 组装 Node
		$this->_PackagingNode($aUITemplate,$sCompiled) ;
		
		
	}
	
	/**
	 * 在模版原文中查找 Node 的标签，并加入到一个栈中
	 * 
	 * @access	public
	 * @param	$aUITemplate				UI		UI模版对象
	 * @param	& $sTemplateStream		string		模版原文的内容
	 * @param	$sTemplatePath				string		模版文件路径
	 * @return	void
	 */
	protected function _FindNodeTag(UI $aUITemplate, &$sTemplateStream,$sTemplatePath)
	{
		// 设置一个 栈
		$this->aNodeTagStack = new Stack('UIHtmlNodeTag') ;
		
		// 所有 一级 节点名称
		$aCompilerManager = $this->GetCompilerManager() ;
		ASSERT::ASSERT_INSTANCE($aCompilerManager,'JCAT\UIObjectProcessorManager') ;
		
		$aIterator = $aCompilerManager->CreateProcessorIterator() ;
		ASSERT::ASSERT_INSTANCE($aIterator,'JCAT\ArrayIterator') ;
		$aIterator->First() ;
		
		$arrNames = array() ;
		while( !$aIterator->IsDone() )
		{
			$sObjectType = $aIterator->CurrentKey() ;
							// 处理一些 正则表达式中 有特殊意义的符号
			$arrNames[] = UIObjectParserBase::EscapeCharacterForRegexp($sObjectType) ;
			$aIterator->Next() ;
		}
		
		// 没有 任何编译器
		if( !count($arrNames) )
			return ;
			
		$sNames = implode('|',$arrNames) ;
		
		$sRegexp = "/#		#
[<\{]# 					# 标签开始边界  < 或 {
(\/?)#						# 尾标签 斜线
(#							# 标签名称 开始 ------
({$sNames})#				# 	一级名称
(:[^\s\>\}]+)?#			# 	后面的名称
)#							# ------ 标签名称 结束
(\s[^>\}]*?)?#			# 标签属性
\/?#						# 单行标签结束斜线
[\}>]#						# 标签结束边界
/isx" ;
		// echo $sRegexp . "\r\n" ;
		
		$nNodeNameIdx = 2 ;			// 标签名称  位置
		$nNodeTopNameIdx = 3 ;		// 标签顶级名称  位置
		$nTailSlasheIdx = 1 ;		// 尾标签斜线 位置
		$nTagAttributeIdx = 5 ;		// 标签属性 位置
		

		// 依次创建标签对象
		if( preg_match_all( $sRegexp, $sTemplateStream, $arrRes ) )
		{
			$nStartFindPos = 0 ;

			foreach($arrRes[0] as $nIdx=>&$sTagSource)
			{
				$sNodeName = $arrRes[$nNodeNameIdx][$nIdx] ;
				$sNodeTopName = $arrRes[$nNodeTopNameIdx][$nIdx] ;
				$nNodeType = ($arrRes[$nTailSlasheIdx][$nIdx]==='/')? (UIHtmlNodeTag::TYPE_TAIL): (UIHtmlNodeTag::TYPE_HEAD) ;
				
				// 将 节点名称 统一为小写
				$sNodeName = strtolower($sNodeName) ;
				$sNodeTopName = strtolower($sNodeTopName) ;

				// 创建对象
				$aTag = new UIHtmlNodeTag($sTagSource,$sNodeName,$nNodeType) ;
				
				// 头标签，创建一个
				if($aTag->GetTagType()==UIHtmlNodeTag::TYPE_HEAD)
				{
					$aTag->SetTagAttributeSource($arrRes[$nTagAttributeIdx][$nIdx]) ;
				}

				// 定位
				$aTag->Locate( $sTemplateStream, $nStartFindPos ) ;
				$aTag->SetTemplateFile($sTemplatePath) ;
				$nStartFindPos = $aTag->GetEndByte() + 1 ;
				
				// 进一步装配 UI对象...
				$aTag->SetTemplate($aUITemplate) ;		// 所属模版
				$aTag->SetParser($this) ;				// 分析器
				// $aTag->PutInCompiler(null) ;				// 将 属性分析器 设为 第一个编译器

				// 加入到 标签栈
				$this->aNodeTagStack->In($aTag) ;
			}
		}

	}
	
	/**
	 * 从标签栈中 取出标签，组装一个 Node
	 * 
	 * @access	public
	 * @param	$aUITemplate			UI		UI模版对象
	 * @param	&  $sTemplateStream		string		模版原文的内容
	 * @return	void
	 */
	protected function _PackagingNode(UI $aUITemplate, &$sTemplateStream)
	{
		ASSERT::ASSERT_INSTANCE($this->aNodeTagStack,'JCAT\Stack') ;
		
		// 尾标签栈
		$aTailStack = new Stack('UIHtmlNodeTag') ;
		
		// 载入 节点属性 分析器
							
		// 依次处理所有标签
		while( $aTag = $this->aNodeTagStack->Out() )
		{
			// 尾标签，加入到 $aTailStack 中
			if( $aTag->GetTagType()==UIHtmlNodeTag::TYPE_TAIL )
			{
				$aTailStack->In($aTag) ;
				continue ;
			}
		
			// 查询到对该节点负责的 编译器
			$aCompiler = $this->QueryCompilerByNodeName( $aTag->GetTagName() ) ;
			ASSERT::ASSERT_INSTANCE($aCompiler,'JCAT\IUIHtmlNodeCompiler',Language::SentenceEx('无法查询到节点 %s 的编译器','JCAT',null,$aTag->GetTagName())) ;

			// 从尾标签栈 取出一项
			$aTailTag = $aTailStack->Out() ;
			
			// 单标签 节点
			if( !$aTailTag or !$aTag->MatchTail($aTailTag) )
			{
				$callback = array(get_class($aCompiler),'QueryCanbeSingleTag') ;
				if( !call_user_func_array($callback,array($aTag->GetTagName())) )
				{
					throw new UIHtmlObjectException( $aTag
						, Language::SentenceEx('%s 类型节点 必须成对使用，没有找到对应的尾标签; %s','JCAT',null
							, $aTag->GetTagName(), $aTag->GetLocationDescription()), Exception::MakeExceptionCode(__CLASS__,1)
						) ;
				}

				// 退回 栈中
				if($aTailTag)
				{
					$aTailStack->In($aTailTag) ;
				}
					
				// 创建节点
				$aNode = new UIHtmlNode( $aTag->GetSource(), $aTag->GetTagName() ) ;

				// 装配节点
				$aNode->SetStartByte( $aTag->GetStartByte() ) ;
				$aNode->SetEndByte( $aTag->GetEndByte() ) ;
				$aNode->SetStartLine( $aTag->GetStartLine() ) ;
				$aNode->SetEndLine( $aTag->GetEndLine() ) ;
				$aNode->SetStartByteInLine( $aTag->GetStartByteInLine() ) ;
				$aNode->SetEndByteInLine( $aTag->GetEndByteInLine() ) ;
				$aNode->SetTemplateFile($aTag->GetTemplateFile()) ;

			}
			
			// 成对标签
			else
			{
				// 根据 头标签开始 和 尾标签结束，取得 整个 节点内容
				$nStart = $aTag->GetStartByte() ;
				$nLen = $aTailTag->GetEndByte() - $nStart + 1 ;
				$sNodeSource = substr($sTemplateStream,$nStart,$nLen) ;
				
				// 创建节点
				$aNode = new UIHtmlNode( $sNodeSource, $aTag->GetTagName() ) ;

				// 装配节点
				$aNode->SetStartByte( $aTag->GetStartByte() ) ;
				$aNode->SetEndByte( $aTailTag->GetEndByte() ) ;
				$aNode->SetStartLine( $aTag->GetStartLine() ) ;
				$aNode->SetEndLine( $aTailTag->GetEndLine() ) ;
				$aNode->SetStartByteInLine( $aTag->GetStartByteInLine() ) ;
				$aNode->SetEndByteInLine( $aTailTag->GetEndByteInLine() ) ;
				$aNode->SetTemplateFile($aTag->GetTemplateFile()) ;


				// 创建 Body 对象，并加入到 对象树中
				$nStart = $aTag->GetEndByte() + 1;
				$nLen = $aTailTag->GetStartByte() - $nStart ;
				if($nLen>0)
				{
					// 创建
					$aBody = new UIHtmlObject( substr($sTemplateStream,$nStart,$nLen) ) ;
					$aBody->Locate($sTemplateStream,$nStart) ;		// 定位
					
					// 装配
					$aBody->SetTemplate($aUITemplate) ;						// 所属模版
					$aBody->SetParser($this) ;								// 分析器
					//$aBody->PutInCompiler(null) ;								// 编译器
					
					// 加入
					$aNode->AddUIObject($aBody) ;							// 入树
				}
			}
			
			ASSERT::ASSERT_INSTANCE($aNode,'JCAT\UIHtmlNode') ;
			
			// 为 节点属性创建 UI对象
			$aAttribute = new UIHtmlNodeAttribute( $aTag->GetTagAttributeSource() ) ;
			$aAttribute->Locate($sTemplateStream,0) ;						// 定位
			$aAttribute->SetTemplate($aUITemplate) ;						// 所属模版
			$aAttribute->SetParser($this) ;									// 分析器
			$aAttribute->PutInCompiler('UIHtmlNodeAttributeParser') ;	// 编译器
			
			$aNode->AddUIObject($aAttribute) ;
					
			
			// 装配节点
			$aNode->SetTemplate($aUITemplate) ;								// 所属模版
			$aNode->SetParser($this) ;										// 分析器
			$aNode->PutInCompiler($aCompiler) ;								// 编译器
			
			// 加入到节点树
			$aUITemplate->PutInUIObject($aNode) ;
		}
	}
	
	
	
	/**
	 * 根据节点名称 查询 编译器
	 *
	 * @access	public
	 * @param	$sNodeName	string	节点名称
	 * @return	IUIHtmlNodeCompiler, null 
	 */
	public function QueryCompilerByNodeName( $sNodeName )
	{
		ASSERT::ASSERT_STRING($sNodeName) ;
		$arrNames = explode(':',$sNodeName) ;
		
		// 编译器管理器
		$aCompilerManger = $this->GetCompilerManager() ;
		
		$aReturnCompiler = null ;
		
		// 迭代 查询
		while( $sName = array_shift($arrNames) and $aCompilerManger )
		{
			// 查询到 编译器
			$aCompiler = $aCompilerManger->GetProcessor( $sName ) ;
		
			if( !$aCompiler )
				break ;
			
			ASSERT::ASSERT_INSTANCE($aCompiler,'JCAT\IUIHtmlNodeCompiler') ;
			$aCompilerManger = $aCompiler->GetCompilerManager() ;
			$aReturnCompiler = $aCompiler ;
		}
		
		return $aReturnCompiler ;
	}
	

	/**
	 * 注册到处理器管理器
	 * 
	 * @access	public
	 * @param	$aManager	IUIObjectProcessorManager	处理器管理器
	 * @static
	 * @return	void
	 */
	static public function RegistorToProcessorManager( IUIObjectProcessorManager $aManager )
	{
		// 仅仅将类名注册到 处理器管理器中， 将创建实例 延迟到 使用时
		$aManager->RegisterProcessor( 'UI.Html.Node', __CLASS__ ) ;
	}

	


	// 属性 ///////////////////////////////////////////////////////////////////////////////

	/**
	 * What's this Attribute ?
	 * 
	 * @access	private
	 * @var		array
	 * @static
	 */
	static private $arrCompilerDirs = array() ;
	
	/**
	 * What's this Attribute ?
	 * 
	 * @access	private
	 * @var		Stack
	 * @static
	 */
	static private $aNodeTagStack ;


}

UIHtmlNodeParser::AddCompilerDir( dirname(__FILE__).'/../Compiler/NodeCompiler/' ) ;

/*macro_exception_code:1*/
?>